<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
  
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  
  <link href="../css/style.css" rel="stylesheet" type="text/css">

</head>
<body>
<h1>4.14. Стек</h1>
<p class="article">
Стеком называется структура данных, организованная по принципу LIFO («Last In — First Out» или «последним пришёл — первым ушёл»). Стек является неотъемлемой частью архитектуры процессора и поддерживается на аппаратном уровне: в процессоре есть специальные регистры (SS, BP, SP) и команды для работы со стеком.
Обычно стек используется для сохранения адресов возврата и передачи аргументов при вызове процедур (о процедурах в следующей части), также в нём выделяется память для локальных переменных. Кроме того, в стеке можно временно сохранять значения регистров.
Схема организации стека в процессоре 8086 показана на рисунке:
</p>
<div class="image">
	<img src="../img/4.14_1.png" />
</div>
<p class="article">
Стек располагается в оперативной памяти в сегменте стека, и поэтому адресуется относительно сегментного регистра SS. Шириной стека называется размер элементов, которые можно помещать в него или извлекать. В нашем случае ширина стека равна двум байтам или 16 битам. Регистр SP (указатель стека) содержит адрес последнего добавленного элемента. Этот адрес также называется вершиной стека. Противоположный конец стека называется дном.
</p>
<p class="article">
Дно стека находится в верхних адресах памяти. При добавлении новых элементов в стек значение регистра SP уменьшается, то есть стек растёт в сторону младших адресов. Как вы помните, для COM-программ данные, код и стек находятся в одном и том же сегменте, поэтому если постараться, стек может разрастись и затереть часть данных и кода.
</p>
<div class="notewarning">
Размер стека не резиновый! Может возникнуть ситуация переполнения стека, что 
приведет к фатальной ошибке.
</div>
<div class="noteclassic">
В некоторой литературе стек так же называют магазином по аналогии с магазином в огнестрельном оружии (стрельба начнётся с патрона, заряженного последним)
</div>
<p class="article">
Для стека существуют всего две основные операции:
</p>
<ul>
	<li class="article list">добавление элемента на вершину стека (PUSH);</li>
	<li class="article list">извлечение элемента с вершины стека (POP);</li>
</ul>
<h2>Добавление элемента в стек</h2>
<p class="article">
Выполняется командой PUSH. У этой команды один операнд, который может быть непосредственным значением, 16-битным регистром (в том числе сегментым) или 16-битной переменной в памяти. Команда работает следующим образом:
</p>
<ol>
	<li class="article">значение в регистре SP уменьшается на 2 (так как ширина стека — 16 бит или 2 байта);
	</li>
	<li class="article">операнд помещается в память по адресу в SP.</li>
</ol>
<div class="image">
	<img src="../img/4.14_2.png" />
</div>
<p class="article">
Примеры:
</p>
<pre class="code">
    push -5           ;Поместить -5 в стек
    push ax           ;Поместить AX в стек
    push ds           ;Поместить DS в стек
    push [x]          ;Поместить x в стек (x объявлен как слово)
    push word [bx]    ;Поместить в стек слово по адресу в BX
</pre>
<p class="article">
Существуют ещё 2 команды для добавления в стек. Команда PUSHF помещает в стек содержимое регистра флагов. Команда PUSHA помещает в стек содержимое всех регистров общего назначения в следующем порядке: АХ, СХ, DX, ВХ, SP, BP, SI, DI (значение DI будет на вершине стека). Значение SP помещается то, которое было до выполнения команды. Обе эти команды не имеют операндов.
</p>
<h2>Извлечение элемента из стека</h2>
<p class="article">
Выполняется командой POP. У этой команды также один операнд, который может быть 16-битным регистром (в том числе сегментым, но кроме CS) или 16-битной переменной в памяти. Команда работает следующим образом:
</p>
<ol>
	<li class="article">операнд читается из памяти по адресу в SP;</li>
	<li class="article">значение в регистре SP увеличивается на 2.</li>
</ol>
<p class="article">
Обратите внимание, что извлеченный из стека элемент не обнуляется и не затирается в памяти, а просто остаётся как мусор. Он будет перезаписан при помещении нового значения в стек.
</p>
<div class="image">
	<img src="../img/4.14_3.png" />
</div>
<p class="article">
Примеры:
</p>
<pre class="code">
    pop cx            ;Поместить значение из стека в CX
    pop es            ;Поместить значение из стека в ES
    pop [x]           ;Поместить значение из стека в переменную x
    pop word [di]     ;Поместить значение из стека в слово по адресу в DI
</pre>
<p class="article">
Соответственно, есть ещё 2 команды. POPF помещает значение с вершины стека в регистр флагов. POPA восстанавливает из стека все регистры общего назначения (но при этом значение для SP игнорируется).
</p>
<h2>Пример программы</h2>
<p class="article">
Имеется двумерный массив — таблица 16-битных значений со знаком размером n строк на m столбцов. Программа вычисляет сумму элементов каждой строки и сохраняет результат в массиве sum. Первый элемент массива будет содержать сумму элементов первой строки, второй элемент — сумму элементов второй строки и так далее.
</p>
<pre class="code">
use16                 ;Генерировать 16-битный код
org 100h              ;Программа начинается с адреса 100h
    jmp start         ;Переход к метке start
;----------------------------------------------------------------------
; Данные
n   db 4              ;Количество строк
m   db 5              ;Количество столбцов
;Двумерный массив - таблица c данными
table:
    dw 12,45, 0,82,34
    dw 46,-5,87,11,56
    dw 35,21,77,90,-9
    dw 44,13,-1,99,32
sum rw 4              ;Массив для сумм каждой строки
;----------------------------------------------------------------------
start:
    movzx cx,[n]      ;Счётчик строк
    mov bx,table      ;BX = адрес таблицы
    mov di,sum        ;DI = адрес массива для сумм
    xor si,si         ;SI = смещение элемента от начала таблицы
 
rows:
    xor ax,ax         ;Обнуление AX. В AX будет считаться сумма
    push cx           ;Сохранение значения CX
 
    movzx cx,[m]      ;Инициализация CX для цикла по строке
calc_sum:
    add ax,[bx+si]    ;Прибавление элемента строки
    add si,2          ;SI = смещение следующего элемента
    loop calc_sum     ;Цикл суммирования строки
 
    pop cx            ;Восстановление значения CX
    mov [di],ax       ;Сохранение суммы строки
    add di,2          ;DI = адрес следующей ячейки для суммы строки
    loop rows         ;Цикл по всем строкам таблицы
 
    mov ax,4C00h      ;\
    int 21h           ;/ Завершение программы
</pre>
<p class="article">
Как видите, в программе два вложенных цикла: внешний и внутренний. Внешний цикл — это цикл по строкам таблицы. Внутренний цикл вычисляет сумму элементов строки. Стек здесь используется для временного хранения счётчика внешнего цикла. Перед началом внутреннего цикла CX сохраняется в стеке, а после завершения восстанавливается. Такой приём можно использовать для программирования и большего количества вложенных циклов.
</p>
</body>
</html>
